﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Aquarinum.Base.Infrastructure.Ioc;
using Aquarinum.Base.Infrastructure.WebSocket.Extensions;
using Aquarinum.Base.Infrastructure.WebSocket.Interface;
using Aquarinum.Util.Interface.Log;
using Aquarinum.WebSocekt.Core.Dtos;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using QueryString = Aquarinum.Base.Infrastructure.WebSocket.Utils.QueryString;

namespace Aquarinum.Base.Infrastructure.WebSocket
{
    public class WebSocketManagerMiddleware
    {
        private readonly RequestDelegate _next;

        private readonly IWebSocketRequestTransfer _webSocketRequestTransfer;

        public WebSocketManagerMiddleware(RequestDelegate next)
        {
            _next = next;
            _webSocketRequestTransfer = AquainumContainer.Resolve<IWebSocketRequestTransfer>();
        }

        public async Task Invoke(HttpContext context)
        {
            if (!context.WebSockets.IsWebSocketRequest)
                return;

            #region Jwt校验

            IDictionary<string, dynamic> jwtTokenDic = null;
            var queryString =
                QueryString.Parse(context.Request.QueryString.ToString()
                    .Replace("?", string.Empty));
            queryString.TryGetValue("token", out var token);
            HandShakeDto handShakeDto = null;
            if (!string.IsNullOrEmpty(token)) //获取不到Token 验证不通过
            {
                jwtTokenDic = JwtTokenHelper.DecodeJwtToken(token);

                if (jwtTokenDic != null && jwtTokenDic.ContainsKey("handShake"))
                {
                    var json = jwtTokenDic["handShake"].ToString();
                    handShakeDto = JsonConvert.DeserializeObject<HandShakeDto>(json);
                }
            }

            #endregion

            var socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
            await _webSocketRequestTransfer.OnConnected(socket, handShakeDto, context).ConfigureAwait(false);

            await Receive(socket, async (result, serializedInvocationDescriptor) =>
            {
                if (result.MessageType == WebSocketMessageType.Text)
                    await _webSocketRequestTransfer.ReceiveAsync(socket, serializedInvocationDescriptor, context)
                        .ConfigureAwait(false);

                if (result.MessageType == WebSocketMessageType.Close)
                    try
                    {
                        await _webSocketRequestTransfer.OnDisconnected(socket);
                    }
                    catch (WebSocketException ex)
                    {
                        AquainumContainer.Resolve<IAquarinumLogger>().Error($"WebSocket关闭连接发生错误", ex);
                    }
            });

            //TODO - investigate the Kestrel exception thrown when this is the last middleware
            //await _next.Invoke(context);
        }

        private async Task Receive(System.Net.WebSockets.WebSocket socket,
            Action<WebSocketReceiveResult, string> handleMessage)
        {
            while (socket.State == WebSocketState.Open)
            {
                var buffer = new ArraySegment<byte>(new byte[1024 * 4]);
                string serializedInvocationDescriptor = null;
                WebSocketReceiveResult result = null;
                using (var ms = new MemoryStream())
                {
                    do
                    {
                        result = await socket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
                        ms.Write(buffer.Array, buffer.Offset, result.Count);
                    } while (!result.EndOfMessage);

                    ms.Seek(0, SeekOrigin.Begin);

                    using (var reader = new StreamReader(ms, Encoding.UTF8))
                    {
                        serializedInvocationDescriptor = await reader.ReadToEndAsync().ConfigureAwait(false);
                    }
                }

                handleMessage(result, serializedInvocationDescriptor);
            }
        }
    }
}